#pragma once

#include<iostream>
#include<cstdio>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<cstring>
#include<vector>
#include<cstdlib>
#include<ctime>
#include"task.hpp"

using namespace std;

enum //匿名枚举，用来将错误码转化为错误信息
{
    UsageError = 1,
    ProcnumError,
    PipeError
};

void Usage(const string& proc)
{
    cout << "Usage:" << proc << "proc_num" << endl;
    return ;
}

class Channel //先描述(类)
{
public:
    Channel(int wfd, int id, string& name)
        :_wfd(wfd),_id(id),_name(name)
    { }

    int Wfd(){ return _wfd; }

    string& Name(){ return _name;}

    int Id(){ return _id; }

    void Print()
    {
        cout << "_wfd: " << _wfd;
        cout << ",_id: " << _id;
        cout << ",_name: " << _name << endl;
    }

    ~Channel(){ }
private:
    int _wfd;
    pid_t _id;
    string _name;
};


class Processpol //进程池
{
public:
    Processpol(int proc_num) 
        :_proc_num(proc_num)
    { }

    //vector<int> fds; //用来关闭struct file被多个文件描述符指向
    int CreatProcess(work_t work) //1.创建一组子进程、命令行管道
    {
        for(int i = 0; i < _proc_num; i++)
        {
            //父进程需要对管道进行管理,否则会被覆盖
            int pipefd[2]{0}; 
            int n = pipe(pipefd);
            if(n == -1) return PipeError; //创建管道失败
            
            pid_t id = fork(); 
            if(id == 0) //子进程
            {
                // if (!fds.empty()) 
                //     for (auto& fd : fds) close(fd);

                close(pipefd[1]); //关闭管道的写端
                dup2(pipefd[0], 0); //将标准输入重定向到管道读端
                work(pipefd[0]);  //执行任务 —— 回调函数
                exit(0); //终止
            }
            //父进程
            close(pipefd[0]); //关闭管道的读端

            string name = "channel-" + to_string(i); //管道名字
            _channels.push_back(Channel(pipefd[1], id, name)); 

            //fds.push_back(pipefd[1]);
        }
        return 0;
    }

    //2.1.选择一个子进程、管道，来执行任务 —— 父进程'负载均衡'式的给子进程分配任务
    int NextChannel()
    {
        static int next = 0; //静态,不属于任何对象,全局, 用来定位进程池中的各个进程、管道
        int n = next;
        next++;
        next %= _channels.size(); //不可越界
        return n;
    }

    //2.3.发送任务编号到管道写端，以供子进程读取
    void SendTaskCode(int c_code, int t_code)
    {
        cout << "send code " << t_code << " to " << _channels[c_code].Name() << " proc_id " << _channels[c_code].Id() << endl;
        write(_channels[c_code].Wfd(), &t_code, sizeof(t_code)); //父进程将数据写入管道的写端
        sleep(1);
    }
    
    //3.回收资源
    /*因为子进程会继承父进程的文件描述符表，子进程会将父进程中以w方式打开上一文件的fd,
    使得上一文件被多个指针指向，所以关闭父进程写端，此文件的struct file中引用计数--,并未全部关闭*/
    void RecycleProc()
    {
        for(auto& e : _channels)
        {
            close(e.Wfd()); //关闭当前被打开文件(管道)的写端
            int rid = waitpid(e.Id(), NULL, 0); //回收子进程资源
            if(rid > 0) //等待成功
                cout << "wait process " << e.Id() << "sucess" << endl;
            cout << e.Name() << " close done. process " << e.Id() <<"quit now!" << endl; 
        }
    }

    void Debug() //打印子进程、命令行管道相关信息
    {
        for(auto& e : _channels)
        {
            e.Print();
        }
    }

    ~Processpol(){ }

private:
    vector<Channel> _channels; //再组织
    int _proc_num; //预先创建的子进程数量
};

void CtrlProcessPool(Processpol* ptr, int cnt)
{
    while(cnt--)
    {
        int c_code = ptr->NextChannel();
        int t_code = NextCode();
        ptr->SendTaskCode(c_code, t_code);
    }
}

int main(int argc, char* argv[])
{
    //设置随机数生成的种子(起始值),若无srand，则每次程序启动随机数序列均相同，使用当前时间(时间戳)来初始化种子
    srand((uint64_t)time(nullptr)); //unit64_t为无符号64位整数,long long别名

    //输入错误
    if(argc != 2) 
    {
        Usage(argv[0]);
        return UsageError;
    }

    //预设创建的进程数须>0
    int proc_num = stoi(argv[1]);
    if(proc_num <= 0) 
        return ProcnumError;
    
    Processpol* ptr = new Processpol(proc_num); //创建一个进程池
    //1.创建一组子进程、命令行管道
    ptr->CreatProcess(Work);
    ptr->Debug(); //打印子进程、命令行管道相关信息

    //2.控制子进程
    CtrlProcessPool(ptr, 5);
    cout << "task finish......" << endl;

    //3.回收资源: 子进程先要退出(关闭写端),再回收子进程资源
    ptr->RecycleProc();
    
    return 0;
}
